package com.yesin.elasticsearch.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yesin.elasticsearch.client.BrandClient;
import com.yesin.elasticsearch.client.CategoryClient;
import com.yesin.elasticsearch.client.GoodsClient;
import com.yesin.elasticsearch.client.SpecificationClient;
import com.yesin.elasticsearch.pojo.Goods;
import com.yesin.elasticsearch.pojo.SearchRequest;
import com.yesin.elasticsearch.pojo.SearchResult;
import com.yesin.elasticsearch.repository.GoodsRepository;
import com.yesin.item.pojo.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.AbstractQueryBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {
    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsRepository goodsRepository;

    private static ObjectMapper mapper = new ObjectMapper();

    public Goods buildGoods(Spu spu) throws IOException {
        // 根据分类的id查询分类名称
        List<String> names = this.categoryClient.queryNamesByIds(
                Arrays.asList(
                        spu.getCid1(), spu.getCid2(), spu.getCid3()));
        // 根据品牌id查询品牌名称
        Brand brand = this.brandClient.queryBrandById(spu.getBrandId());
        // 根据spu的id查询所有sku
        List<Sku> skus = this.goodsClient.querySkuListBySpuId(spu.getId());
        // 初始化一个价格集合，收集所有sku价格
        List<Long> prices = new ArrayList<>();
        // 收集所有sku必要字段
        List<Map<String, Object>> skuMapList = new ArrayList<>();
        skus.forEach(sku -> {
            prices.add(sku.getPrice());
            Map<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("price", sku.getPrice());
            // 获取sku中的图片，数据库的图片可能是多张，多张是以","分隔
            // 所以用逗号来切割返回图片数组，获取第一张图片
            map.put("image", StringUtils.isBlank(sku.getImages()) ? "" :
                    StringUtils.split(sku.getImages(), ",")[0]);
            skuMapList.add(map);
        });
        // 根据spu中的cid3查询出所有的搜索参数
        List<SpecParam> params = this.specificationClient.
                queryParams(null, spu.getCid3(), null, true);
        // 根据spuId查询spuDetail
        SpuDetail spuDetail = this.goodsClient.querySpuDetailById(spu.getId());
        // 拿到genericSpec
        Map<Long, Object> genericSpecMap =
                mapper.readValue(spuDetail.getGenericSpec(),
                        new TypeReference<Map<Long, Object>>() {
                        });
        // 拿到specialSpec
        Map<Long, List<Object>> specialSpecMap =
                mapper.readValue(spuDetail.getSpecialSpec(),
                        new TypeReference<Map<Long, List<Object>>>() {
                        });
        // 定义一个map容器接收(规格参数名,规格参数值)
        Map<String, Object> paramMap = new HashMap<>();
        params.forEach(param -> {
            // 判断是否是通用类型参数
            if (param.getGeneric()) {
                // 获取规格参数值
                String value = genericSpecMap.get(param.getId()).toString();
                // 判断是否是数值类型
                if (param.getNumeric()) {
                    value = chooseSegment(value, param);
                }
                paramMap.put(param.getName(), value);
            } else {
                paramMap.put(param.getName(), specialSpecMap.get(param.getId()));
            }
        });
        Goods goods = new Goods();
        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        goods.setSubTitle(spu.getSubTitle());
        // 拼接All字段，需要分类名称以及品牌名称,用空格分隔
        goods.setAll(spu.getTitle() + " "
                + StringUtils.join(names, " ") + " " + brand.getName());
        // 获取spu下所有sku的价格
        goods.setPrice(prices);
        // 获取spu下的所有sku并转化成json字符串
        goods.setSkus(mapper.writeValueAsString(skuMapList));
        // 获取所有查询的规格参数{name:value}
        goods.setSpecs(paramMap);
        return goods;
    }

    public static String chooseSegment(String value, SpecParam p) {
        Double v = NumberUtils.toDouble(value);
        String result = "";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            Double begin = NumberUtils.toDouble(segs[0]);
            Double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if (v >= begin && v < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
            }
        }
        return result;
    }

    public SearchResult search(SearchRequest request) {
        // 判断是否有搜索条件，没有返回null
        if (StringUtils.isBlank(request.getKey())) {
            return null;
        }
        // 自定义查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加查询条件 1. 对key进行全文检索查询
//        MatchQueryBuilder basicQuery = QueryBuilders.
//                matchQuery("all", request.getKey()).operator(Operator.AND);
        BoolQueryBuilder basicQuery = getBoolQueryBuilder(request);
        queryBuilder.withQuery(basicQuery);
        // 2. 通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(
                new String[]{"id", "skus", "subTitle"}, null));
        // 3. 分页
        // 准备分页参数
        int page = request.getPage();
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page - 1, size));

        // 4. 排序
        String sortBy = request.getSortBy();
        Boolean desc = request.getDesc();
        if (StringUtils.isNotBlank(sortBy)) {
            queryBuilder.withSort(SortBuilders.fieldSort(sortBy).
                    order(desc ? SortOrder.DESC : SortOrder.ASC));
        }

        // 5. 添加分类和品牌的聚合
        String categoryAggName = "category";
        String brandAggName = "brands";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        // 查询，获取结果
        AggregatedPage<Goods> pageInfo = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());

        // 解析聚合结果集
        List<Map<String, Object>> categories = getCategoryAggResult(pageInfo.getAggregation(categoryAggName));
        List<Brand> brands = getBrandAggResult(pageInfo.getAggregation(brandAggName));

        List<Map<String, Object>> specs = null;
        // 判断是否是一个分类，只有一个分类时才做规格参数聚合
        if (!CollectionUtils.isEmpty(categories) && categories.size() == 1){
            // 对规格参数进行聚合
             specs = getParamAggResult((Long) categories.get(0).get("key"), basicQuery);
        }

        // 封装结果并返回
        return new SearchResult(pageInfo.getTotalElements(), pageInfo.getTotalPages(),
                pageInfo.getContent(), categories, brands, specs);
    }

    /**
     * bool查询构建器
     * @param request
     * @return
     */
    private BoolQueryBuilder getBoolQueryBuilder(SearchRequest request) {

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        if (CollectionUtils.isEmpty(request.getFilter())){
            return queryBuilder;
        }
        request.getFilter().entrySet().forEach(entry -> {
            String key = entry.getKey();
            if (StringUtils.equals(key, "品牌")){
                key = "brandId";
            }else if (StringUtils.equals(entry.getKey(), "分类")){
                key = "cid3";
            }else {
                // 如果是规格参数名，过滤字段名：specs.key.keyword
                key = "specs." + entry.getKey() + ".keyword";
            }
            queryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        });
        return queryBuilder;
    }

    /**
     * 构建规格参数的聚合
     * @param cid
     * @param basicQuery
     * @return
     */
    private List<Map<String, Object>> getParamAggResult(Long cid, AbstractQueryBuilder basicQuery) {
        // 创建自定义查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 基于基本的查询条件，聚合规格参数
        queryBuilder.withQuery(basicQuery);
        // 查询要聚合的规格参数
        List<SpecParam> params = this.specificationClient.queryParams(null,cid,null,true);
        // 添加规格参数的聚合
        params.forEach(param ->
             queryBuilder.addAggregation(AggregationBuilders
                     .terms(param.getName()).field("specs." + param.getName() + ".keyword")));
        // 添加结果集过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
        // 执行聚合查询
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());
        // 解析聚合结果集,{key "聚合名称", value " 聚合对象"}
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
        List<Map<String, Object>> paramMapList = new ArrayList<>();
        for (Map.Entry<String, Aggregation> entry: aggregationMap.entrySet()) {
            // 聚合对象 桶 {"k" : "聚合名称", "options" : "聚合数组"}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     }
            Map<String, Object> map = new HashMap<>();
            map.put("k", entry.getKey());
            StringTerms terms = (StringTerms)entry.getValue();
            List<String> options = terms.getBuckets().stream().map(bucket ->
                    bucket.getKeyAsString()).collect(Collectors.toList());
            map.put("options", options);
            paramMapList.add(map);
        }
        return paramMapList;
    }

    /**
     * 解析品牌的聚合结果集
     *
     * @param aggregation
     * @return
     */
    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        LongTerms terms = (LongTerms) aggregation;
        return terms.getBuckets().stream().map(bucket ->
                this.brandClient.queryBrandById(bucket.getKeyAsNumber().longValue()))
                .collect(Collectors.toList());
    }

    /**
     * 获取分类聚合结果集
     *
     * @param aggregation
     * @return
     */
    private List<Map<String, Object>> getCategoryAggResult(Aggregation aggregation) {
        LongTerms terms = (LongTerms) aggregation;
        return terms.getBuckets().stream().map(bucket -> {
            Map<String, Object> map = new HashMap<>();
            Long id = bucket.getKeyAsNumber().longValue();
            List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(id));
            map.put("key", id);
            map.put("name", names.get(0));
            return map;
        }).collect(Collectors.toList());
    }

    /**
     * 保存商品到搜索服务器
     * @param id
     * @throws IOException
     */
    public void save(Long id) throws IOException {
        Spu spu = this.goodsClient.querySpuById(id);
        Goods goods = this.buildGoods(spu);
        this.goodsRepository.save(goods);
    }

    /**
     * 删除索引数据库中指定的商品
     * @param id
     */
    public void delete(Long id) throws IOException {
        this.goodsRepository.deleteById(id);
    }
}
